#! /usr/bin/env python
"""
reppi: reverse polish python interpreter
alrhodes@gmail.com
GNU GPL v2 blah  blah
"""

import cmd, inspect, os

class Stack(list):
  push = list.append

  def __str__(self):
    l = len(self)
    if l == 0: return "stack empty"
    if l == 1: return "%02d: %s" % (l, repr(self[0]))
    s = ""
    for item in self:
      s += "%d: %s" % (l,repr(item))
      if l > 1: s+= '\n'
      l -= 1
    return s

  def rot(self):
    self[-2],self[-1] = self[-1],self[-2]

  def dup(self):
    self.push(self[-1])

  def drop(self):
    self.pop()

class Rip(cmd.Cmd, Stack, object):
  def __init__(self):
    cmd.Cmd.__init__(self)
    self.prompt='#> '

  def preloop(self):
    cmd.Cmd.preloop(self)
    self.hist, self._locals, self._globals = [], {}, {}

  def onecmd(self, line):
    line = line.strip()
    if line == '': return super(Rip, self).onecmd(line)
    # shortcuts
    if   line    == '.'  : line = 'dump'
    elif line[0] == '`'  : line = 'eval ' + line[1:]
    elif line    == '\\' : line = 'drop'
    elif line    == 'd'  : line = 'dup'
    elif line    == 'r'  : line = 'rot'
    elif line    == '+'  : line = 'plus'
    elif line    == '-'  : line = 'minus'
    elif line    == '*'  : line = 'mult'
    elif line    == '/'  : line = 'div'
    elif line    == 'q'  : line = 'quit'
    elif line[0] == 'x'  : line = 'exec' + line[1:]
    elif line    == 'a'  : line = 'appl'
    return super (Rip, self).onecmd(line)

  def do_appl(self, line):
    argc = self.pop()
    func = self.pop()
    args = []
    for i in range(argc):
      args.insert(0,self.pop())
    o = apply( func, tuple(args) )
    self.push(o)

  def do_eval(self, line):
    # eval a python expression and push it
    o = eval(line, self._globals, self._locals)
    #print "pushed: %s : %s" % ( type(o), repr(o) )
    # ??? should pushing a tuple make separate stack entries?
    self.push(o)

  def do_plus(self, line):
    # do the preserving-args stack call thing
    if len(self) < 2:
      print "plus error: not enough arguments"
      return
    r = self[-2] + self[-1]
    self.pop()
    self[-1] = r

  def do_minus(self, line):
    if len(self) <2:
      print "minus error: not enough arguments"
      return
    r = self[-2] - self[-1]
    self.pop()
    self[-1] = r

  def do_mult(self, line):
    if len(self) <2:
      print "mult error: not enough arguments"
      return
    r = self[-2] * self[-1]
    self.pop()
    self[-1] = r

  def do_div(self, line):
    if len(self) <2:
      print "div error: not enough arguments"
      return
    try:
      r = self[-2] / self[-1]
    except ZeroDivisionError:
      print "div error: zero division fail"
      return
    self.pop()
    self[-1] = r

  def do_dump(self, line):
    print str(self)

  def do_rot(self, line):
    try:
      self.rot()
    except IndexError:
      print "rot error: not enough arguments"

  def do_dup(self, line):
    if len(self) < 1:
      print "dup error: not enough arguments"
      return
    self.dup()

  def do_drop(self, line):
    try:
      self.pop()
    except IndexError:
      print "drop error: not enough arguments"

  def default(self, line):
    thing = None
    if hasattr(globals(),line):
      thing = getattr(globals(),line)
    elif hasattr( globals()['__builtins__'], line):
      thing = getattr(globals()['__builtins__'],line)
    elif locals().has_key(line):
      thing = locals()[line]
    elif self._globals.has_key(line):
      thing = self._globals[line]
    elif self._locals.has_key(line):
      thing = self._globals[line]
    if thing is None:
      print "could not find: %s" % line
      return
    print "found: %s" % repr(thing)
    #numargs = inspect.getargspec(thing)

  def do_shell(self,line):
    output = os.popen(line).read()
    self.push(output)

  def do_quit(self,line):
    return True

  def do_EOF(self,line):
    return True

  def do_dump(self, line):
    print str(self)

  def do_exec(self,line):
    #exec(line) in locals(), globals()
    exec(line) in self._locals, self._globals

  def emptyline(self):
    pass

if __name__ == '__main__':
  Rip().cmdloop()
